﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ParallelRuntimeLibrary.Internal;

namespace ParallelRuntimeLibrary
{
    public struct ReaderWriterLockSlim
    {
        private int mState;
        private SpinWait mWaiter;
        
        private const int MASK_WRITER_BIT = unchecked((int)0x80000000);
        private const int MASK_WRITER_WAITING_BIT = unchecked((int)0x40000000);
        private const int MASK_WRITER_BITS = unchecked((int)(MASK_WRITER_BIT | MASK_WRITER_WAITING_BIT));
        private const int MASK_READER_BITS = unchecked((int)~MASK_WRITER_BITS);


        public void EnterWriteLock()
        {
            mWaiter.Reset();
            do
            {
                // If there are no readers currently, grab the write lock.
                int state = mState;
                if ((state == 0 || state == MASK_WRITER_WAITING_BIT) &&
                    Interlocked.CompareExchange(ref mState, MASK_WRITER_BIT, state) == state)
                    return;

                // Otherwise, if the writer waiting bit is unset, set it.  We don't
                // care if we fail -- we'll have to try again the next time around.
                if ((state & MASK_WRITER_WAITING_BIT) == 0)
                    Interlocked.CompareExchange(ref mState, state | MASK_WRITER_WAITING_BIT, state);

                mWaiter.SpinOnce();
            }
            while (true);
        }

        public void ExitWriteLock()
        {
            // Exiting the write lock is simple: just set the state to 0.  We
            // try to keep the writer waiting bit to prevent readers from getting
            // in -- but don't want to resort to a CAS, so we may lose one.
            Interlocked.Exchange(ref mState, 0 | (mState & MASK_WRITER_WAITING_BIT));
        }

        public void EnterReadLock()
        {
            mWaiter.Reset();
            do
            {
                int state = mState;
                if ((state & MASK_WRITER_BITS) == 0)
                {
                    if (Interlocked.CompareExchange(ref mState, state + 1, state) == state)
                        return;
                }

                mWaiter.SpinOnce();
            }
            while (true);
        }

        public void ExitReadLock()
        {
            mWaiter.Reset();
            do
            {
                // Validate we hold a read lock.
                int state = mState;
                if ((state & MASK_READER_BITS) == 0)
                    throw new Exception("Cannot exit read lock when there are no readers");

                // Try to exit the read lock, preserving the writer waiting bit (if any).
                if (Interlocked.CompareExchange(
                    ref mState, ((state & MASK_READER_BITS) - 1) | (state & MASK_WRITER_WAITING_BIT), state) == state)
                    return;

                mWaiter.SpinOnce();
            }
            while (true);
        }
    }
}
